package org.nViron.nvSim;

//~--- non-JDK imports --------------------------------------------------------

import org.nViron.nvCommon.nvPosition;
import org.nViron.nvConfig.nvConfig;
import org.nViron.nvData.nvDataEntity;
import org.nViron.nvData.nvDataEntityMgr;
import org.nViron.nvData.nvDataSource;
import org.nViron.nvData.nvDataSourceMgr;
import org.nViron.nvMaps.nvMapSource;
import org.nViron.nvOrganic.nvOrganicsMgr;
import org.nViron.nvOrganic.nvTissueMgr;
import org.nViron.nvParams.nvContextParamMgr;
import org.nViron.nvParams.nvContextParamMgr.CONTEXTPARAM;
import org.nViron.nvParams.nvParamBindingMgr;
import org.nViron.nvParams.nvVizParam;
import org.nViron.nvParams.nvVizParamMgr;
import org.nViron.nvPlaces.nvPlace;
import org.nViron.nvPlaces.nvPlacesMgr;
import org.nViron.nvProjection.nvProjection;
import org.nViron.nvRender.nvRenderer;
import org.nViron.nvRender.nvVec3;
import org.nViron.nvTraces.nvTracesMgr;

/**
 * nViron's base and main class, holds all the stuff together
 * @author Roland Haring <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvSimulator implements nvVisual {
    private static nvSimulator instance;
    public static nvSimulator getInstance() {
        if (instance == null) {
            instance  =  new nvSimulator();
        }
        return instance;
    }
    private String             searchString = new String();
    private float              cameraRotation;
    private nvConfig           config;
    private final nvContextParamMgr  contextParamMgr;
    private boolean            cursorIsActive;
    private nvPosition         cursorPosition;
    private final nvDataSourceMgr    dataSourceMgr;
    private final nvDataEntityMgr    entityMgr;
    private nvMapSource        mapSource;
    private final nvOrganicsMgr      organicsMgr;
    private final nvParamBindingMgr  paramBindingMgr;
    private final nvPlacesMgr        placesMgr;
    private nvProjection       projection;
    private nvRenderer         renderer;
    private float                    skippedDeltaT;
    private final nvTissueMgr        tissueMgr;
    private final nvTracesMgr        traceMgr;

    private final nvVizParamMgr      vizParamMgr;

    protected nvSimulator() {
        contextParamMgr = new nvContextParamMgr();
        vizParamMgr     = new nvVizParamMgr();
        paramBindingMgr = new nvParamBindingMgr(vizParamMgr);
        entityMgr       = new nvDataEntityMgr(this);
        placesMgr       = new nvPlacesMgr(this);
        traceMgr        = new nvTracesMgr(this);
        organicsMgr     = new nvOrganicsMgr(this);
        tissueMgr       = new nvTissueMgr(this);
        dataSourceMgr   = new nvDataSourceMgr(this);
    }

    public final void addDataEntity(nvDataEntity entity) {
        
        synchronized (this) {
            nvPlace place = placesMgr.findPlaceForEntity(entity);
            if (place == null) {    // none found
                place = new nvPlace(placesMgr);
            }
            placesMgr.addEntitiyToPlace(entity, place);
        }
    }

    public int addDataSource(nvDataSource dataSource) {
        return dataSourceMgr.addObject(dataSource);
    }

    public void addMapSource(nvMapSource mapSource) {
        this.mapSource = mapSource;
    }

    public nvPosition getCameraPosition() {
        return config.getCameraPos();
    }

    public nvConfig getConfig() {
        return config;
    }

    public nvContextParamMgr getContextParamMgr() {
        return contextParamMgr;
    }

    public nvPosition getCursorPosition() {
        return cursorPosition;
    }

    public nvDataEntityMgr getDataEntityMgr() {
        return entityMgr;
    }

    nvDataSource getDataSource(int index) {
        return dataSourceMgr.getDataSource(index);
    }

    public nvDataSourceMgr getDataSourceMgr() {
        return dataSourceMgr;
    }

    public nvDataEntityMgr getEntityMgr() {
        return entityMgr;
    }

    public nvPosition getLookAtPosition() {
        return config.getLookAtPos();
    }

    public nvMapSource getMapSource() {
        return mapSource;
    }

    public nvOrganicsMgr getOrganicsMgr() {
        return organicsMgr;
    }

    public nvParamBindingMgr getParamBindingMgr() {
        return paramBindingMgr;
    }

    public nvPlacesMgr getPlacesMgr() {
        return placesMgr;
    }

    public nvProjection getProjection() {
        return projection;
    }

    public nvRenderer getRenderer() {
        return renderer;
    }

    public String getSearchString() {
        return searchString;
    }

    public nvTissueMgr getTissueMgr() {
        return tissueMgr;
    }

    public nvTracesMgr getTraceMgr() {
        return traceMgr;
    }

    public nvPosition getUserPosition() {
        return config.getUserPos();
    }

    public nvVizParamMgr getVizParamMgr() {
        return vizParamMgr;
    }

    public void init(nvConfig config, nvProjection projection) {
        this.config     = config;
        this.projection = projection;
        initDefaults();
        rotCamera(0.0f);
    }

    private void initDefaults() {
        cursorPosition = new nvPosition(config.getUserPos());
        cursorIsActive = false;
    }

    public boolean isCursorIsActive() {
        return cursorIsActive;
    }

    public void loadParams() {
        config.loadParams(paramBindingMgr);
    }

    public void movCamera(float f) {
        nvVec3 cp   = getProjection().geoToViz(getCameraPosition());
        nvVec3 lp   = getProjection().geoToViz(getLookAtPosition());
        float  move = getProjection().geoToVizApprox(f);
        float  x    = (float) Math.sin(cameraRotation / 180.0f * Math.PI) * move;
        float  z    = -(float) Math.cos(cameraRotation / 180.0f * Math.PI) * move;

        cp.x += x;
        cp.z += z;
        lp.x += x;
        lp.z += z;

        nvPosition pos = getProjection().vizToGeo(cp);

        setCameraPosition(pos);
        pos = getProjection().vizToGeo(lp);
        setLookAtPosition(pos);
    }

    public final void removeDataEntity(nvDataEntity entity) {
      
        synchronized (this) {
            nvPlace place = placesMgr.lookupPlaceByEntityId(entity);
            place.removeEntity(entity);
            if (place.getNumEntities() == 0) {
                placesMgr.removePlace(place);
            }
        }
    }

    public void render() {
        if (renderer != null) {
            renderer.render();
        }
    }

    public final synchronized void render(nvRenderer renderer) {
        synchronized (this) {
            entityMgr.render(renderer);
            traceMgr.render(renderer);
            placesMgr.render(renderer);
            organicsMgr.render(renderer);
            tissueMgr.render(renderer);
        }
    }

    public void rotCamera(float degree) {
        float oldAlt = getLookAtPosition().getAltitude();

        cameraRotation += degree;

        nvVec3 la = getProjection().geoToViz(getLookAtPosition());

        la.y = 0.0f;

        nvVec3 cp = getProjection().geoToViz(getCameraPosition());

        cp.y = 0.0f;

        float len = la.distance(cp);
        float x   = (float) Math.sin(cameraRotation / 180.0f * Math.PI) * len;
        float z   = -(float) Math.cos(cameraRotation / 180.0f * Math.PI) * len;

        la.x = cp.x + x;
        la.y = 0;
        la.z = cp.z + z;

        nvPosition newla = getProjection().vizToGeo(la);

        newla.setAltitude(oldAlt);
        setLookAtPosition(newla);
    }

    public void saveParams() {
        config.saveParams(paramBindingMgr);
    }

    public void setCameraPosition(nvPosition cameraPosition) {
        config.setCameraPos(cameraPosition);
    }

    public void setCursorPosition(nvPosition cursorPosition) {
        this.cursorPosition = cursorPosition;
        entityMgr.updateContextValue(CONTEXTPARAM.CURSOR_DIST);
    }

    public void setLookAtPosition(nvPosition lookAtPosition) {
        config.setLookAtPosition(lookAtPosition);
    }

    public void setRenderer(nvRenderer renderer) {
        this.renderer = renderer;
    }

    public void setSearchString(String searchString) {
        this.searchString = searchString;
        entityMgr.updateContextValue(CONTEXTPARAM.SEARCH_RESULT);
    }

    public void setUserPosition(nvPosition userPosition) {
        config.setUserPosition(userPosition);
        entityMgr.updateContextValue(CONTEXTPARAM.USER_DIST);
    }

    public final void update(float timeStep) {
        skippedDeltaT += timeStep;

        if (skippedDeltaT < 1.0f / 30.0f) {
            return;
        } else {
            skippedDeltaT = 0.0f;
        }

        synchronized (this) {
            dataSourceMgr.update(timeStep);
            entityMgr.update(timeStep);
            traceMgr.update(timeStep);
            organicsMgr.update(timeStep);
            placesMgr.update(timeStep);
            tissueMgr.update(timeStep);
        }
    }

    public void updateContextValue(CONTEXTPARAM param) {
        entityMgr.updateContextValue(param);

        for (int i = 0; i < vizParamMgr.size(); i++) {
            nvVizParam vizparam = (nvVizParam) vizParamMgr.get(i);

            if (paramBindingMgr.isRelatedTo(param, vizparam.getKind())) {
                organicsMgr.updateVizValue(vizparam.getKind());
            }
        }
    }

    public final void updateDataEntity(nvDataEntity entity) {
        nvPlace currentPlace;
        nvPlace idealPlace;

        synchronized (this) {
            currentPlace = placesMgr.lookupPlaceByEntityId(entity);
            idealPlace   = placesMgr.findPlaceForEntity(entity);
        }

        if (currentPlace != idealPlace) {    // data entity needs to move to other place (e.g. cause data entity changed position)
            removeDataEntity(entity);
            addDataEntity(entity);

            // TODO generate a trace, that visualizes the movement
        }
    }
}



